import asyncio
import logging
import os
import re
import time
from contextlib import asynccontextmanager
from datetime import datetime
from typing import Optional

import pypandoc
from fastapi import FastAPI, HTTPException, File, UploadFile, Form
from fastapi.responses import Response, FileResponse
from fastapi.concurrency import run_in_threadpool

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("ppt-converter")

# 定义临时文件夹路径
TEMP_DIR = os.getenv("TEMP_DIR", "temp")
if not os.path.exists(TEMP_DIR):
    os.makedirs(TEMP_DIR)


# 智能获取基础URL
def get_base_url() -> str:
    """
    智能获取基础URL，支持各种环境配置：
    1. 优先使用 BASE_URL 环境变量
    2. 如果未设置，尝试使用 HOST 和 PORT 环境变量
    3. 最后使用默认值
    """
    # 1. 直接使用 BASE_URL 环境变量
    base_url = os.getenv("BASE_URL")
    if base_url:
        # 确保URL不以斜杠结尾
        return base_url.rstrip("/")

    # 2. 尝试从 HOST 和 PORT 环境变量构造
    host = os.getenv("HOST", "localhost")
    port = os.getenv("PORT", "8000")

    # 检查是否应该使用 HTTPS
    use_https = os.getenv("USE_HTTPS", "false").lower() == "true"
    scheme = "https" if use_https else "http"

    # 特殊处理：如果端口是标准端口(80/443)，则不包含在URL中
    if (use_https and port == "443") or (not use_https and port == "80"):
        return f"{scheme}://{host}"

    return f"{scheme}://{host}:{port}"


# 全局获取BASE_URL
BASE_URL = get_base_url()
logger.info(f"应用程序基础URL设置为: {BASE_URL}")


def clean_text(text: str) -> str:
    """清理文本，移除无效的 Unicode 字符"""
    return re.sub(r'[\ud800-\udfff]', '', text)


async def cleanup_old_files(max_age_days: int = 3):
    """清理超过指定天数的旧文件"""
    try:
        max_age_days = int(os.getenv("FILE_RETENTION_DAYS", "3"))
        now = time.time()
        cutoff = now - (max_age_days * 24 * 60 * 60)

        for filename in os.listdir(TEMP_DIR):
            file_path = os.path.join(TEMP_DIR, filename)
            if os.path.isfile(file_path):
                file_time = os.path.getmtime(file_path)
                if file_time < cutoff:
                    os.remove(file_path)
                    logger.info(f"清理旧文件: {filename}")
    except Exception as e:
        logger.error(f"清理任务出错: {str(e)}")


async def periodic_cleanup():
    """定期执行清理任务"""
    max_age_days = int(os.getenv("FILE_RETENTION_DAYS", "3"))
    while True:
        logger.info(f"开始执行文件清理任务，保留{max_age_days}天的文件...")
        await cleanup_old_files(max_age_days)
        logger.info(f"清理任务完成，将在24小时后再次运行")
        await asyncio.sleep(24 * 60 * 60)  # 24小时后再次清理


# 使用新的 lifespan 事件处理
@asynccontextmanager
async def lifespan(app: FastAPI):
    """应用生命周期管理"""
    logger.info("应用启动中...")

    # 检查pandoc版本（使用pypandoc_binary）
    try:
        pandoc_version = pypandoc.get_pandoc_version()
        logger.info(f"检测到Pandoc版本: {pandoc_version}")
    except Exception as e:
        logger.error(f"获取Pandoc版本失败: {str(e)}")
        # 即使失败也继续运行，因为pypandoc_binary应该自带pandoc

    # 启动清理任务
    cleanup_task = asyncio.create_task(periodic_cleanup())
    logger.info(f"已启动定时清理任务，将每天清理{os.getenv('FILE_RETENTION_DAYS', '3')}天前的文件")

    yield  # 应用运行期间

    # 应用关闭时
    logger.info("应用关闭中...")
    cleanup_task.cancel()
    try:
        await cleanup_task
    except asyncio.CancelledError:
        logger.info("清理任务已取消")
    logger.info("应用已关闭")


# 使用 lifespan 参数创建 FastAPI 实例
app = FastAPI(lifespan=lifespan)


async def async_convert_markdown_to_pptx(text: str, template_content: Optional[bytes] = None):
    """使用 pypandoc_binary 执行 Markdown 到 PPTX 的转换"""
    pptx_file_path = None
    try:
        logger.info('开始Markdown到PPTX转换')
        text = clean_text(text)
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

        # 生成输出文件名
        pptx_filename = f"output_{timestamp}.pptx"
        pptx_file_path = os.path.join(TEMP_DIR, pptx_filename)

        # 处理模板文件
        template_path = None
        if template_content:
            template_path = os.path.join(TEMP_DIR, f"template_{timestamp}.pptx")
            with open(template_path, "wb") as f:
                f.write(template_content)

        # 创建临时Markdown文件
        md_file_path = os.path.join(TEMP_DIR, f"input_{timestamp}.md")
        with open(md_file_path, "w", encoding="utf-8") as f:
            f.write(text)

        # 使用线程池执行转换
        def run_conversion():
            extra_args = []
            if template_path:
                extra_args = ['--reference-doc', template_path]

            # 使用pypandoc转换
            pypandoc.convert_file(
                md_file_path,
                'pptx',
                outputfile=pptx_file_path,
                extra_args=extra_args
            )

            # 验证文件是否生成
            if not os.path.exists(pptx_file_path):
                raise Exception("PPTX文件未生成")

        await run_in_threadpool(run_conversion)

        # 清理临时文件
        def cleanup_files():
            if os.path.exists(md_file_path):
                os.remove(md_file_path)
            if template_path and os.path.exists(template_path):
                os.remove(template_path)

        await run_in_threadpool(cleanup_files)

        # 返回下载URL
        download_url = f"{BASE_URL}/download/{pptx_filename}"
        return download_url

    except Exception as e:
        logger.exception(f"转换过程中出错: {str(e)}")
        # 清理可能存在的部分生成文件
        if pptx_file_path and os.path.exists(pptx_file_path):
            os.remove(pptx_file_path)
        raise HTTPException(status_code=500, detail=f"转换失败: {str(e)}")


@app.post("/convert/markdown-to-pptx/")
async def convert_markdown_to_pptx(
        text: str = Form(...),
        template_file: Optional[UploadFile] = File(None)
):
    try:
        # 在内存中读取模板文件内容
        template_content = None
        if template_file:
            template_content = await template_file.read()

        # 调用异步转换函数
        download_url = await async_convert_markdown_to_pptx(text, template_content)
        return Response(content=download_url, media_type="text/plain")

    except HTTPException as he:
        raise he
    except Exception as e:
        logger.exception("转换过程中出错")
        raise HTTPException(status_code=500, detail=f"转换失败: {str(e)}")


@app.get("/download/{filename}")
async def download_file(filename: str):
    """提供文件下载（异步线程池版本）"""
    file_path = os.path.join(TEMP_DIR, filename)

    # 安全验证：防止路径遍历攻击
    real_temp = os.path.realpath(TEMP_DIR)
    real_file = os.path.realpath(file_path)
    if not real_file.startswith(real_temp):
        raise HTTPException(status_code=403, detail="禁止访问该文件路径")

    # 使用线程池执行文件操作
    def _get_file_response():
        if not os.path.exists(file_path):
            raise HTTPException(status_code=404, detail="文件不存在或已过期")

        return FileResponse(
            file_path,
            media_type="application/vnd.openxmlformats-officedocument.presentationml.presentation",
            filename=filename
        )

    try:
        return await run_in_threadpool(_get_file_response)
    except Exception as e:
        logger.error(f"文件下载失败: {str(e)}")
        raise HTTPException(status_code=500, detail="文件下载服务暂时不可用")


@app.get("/health")
async def health_check():
    """健康检查端点"""
    pandoc_info = "未获取到版本信息"
    try:
        pandoc_info = pypandoc.get_pandoc_version()
    except Exception as e:
        logger.warning(f"获取Pandoc版本失败: {str(e)}")

    return {
        "status": "healthy",
        "base_url": BASE_URL,
        "pandoc_version": pandoc_info,
        "environment": {
            "BASE_URL": os.getenv("BASE_URL"),
            "HOST": os.getenv("HOST"),
            "PORT": os.getenv("PORT"),
            "USE_HTTPS": os.getenv("USE_HTTPS"),
            "FILE_RETENTION_DAYS": os.getenv("FILE_RETENTION_DAYS"),
            "TEMP_DIR": TEMP_DIR
        }
    }


if __name__ == "__main__":
    import uvicorn

    port = int(os.getenv("PORT", "8000"))
    host = os.getenv("HOST", "0.0.0.0")
    uvicorn.run(app, host=host, port=port)